package org.benetech.collections;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Relates a list of nodes to a tree.
 * @author Reuben Firmin
 * @param <T> the type of the first tree
 * @param <U> the type of the second tree
 */
public final class TreeLinker<T extends Tree, U extends Tree> {

	private T tree1;
	private U tree2;
	private final List<U> tree2Nodes = new ArrayList<U>();
	private Map<T, U> tree1ToTree2 = new LinkedHashMap<T, U>();
	private Map<U, T> tree2ToTree1 = new LinkedHashMap<U, T>();

	/**
	 * Construct a new TreeLinker. The provided trees are expected to be the tops of all other trees handled.
	 * @param tree1 Not null
	 * @param tree2 All nodes to type <U> must belong to this tree
	 */
	public TreeLinker(final T tree1, final U tree2) {
		this.tree1 = tree1;
		this.tree2 = tree2;
	}

	/**
	 * Get the first tree.
	 * @return Never null
	 */
	public T getTree1() {
		return tree1;
	}

	/**
	 *
	 * @return
	 */
	public List<U> getTree2Nodes() {
		return tree2Nodes;
	}

	/**
	 * Get the node linked to this given node, from the other tree structure.
	 * @param relation The node to look up.
	 * @return Null if it isn't linked.
	 */
	public Tree getLinkedNode(final Tree relation) {
		if (relation.getTopOfTree().equals(tree1)) {
			return tree1ToTree2.get(relation);
		} else if (relation.getTopOfTree().equals(tree2)) {
			return tree2ToTree1.get(relation);
		} else {
			throw new RuntimeException("Top of tree is not handled by this linker; " + relation.toString());
		}
	}

	/**
	 * Add a linkage between two nodes; the nodes must be in the two different trees provided in the constructor.
	 * @param node1 Not null
	 * @param node2 Not null
	 */
	public void addLink(final T node1, final U node2) {
		if (node1.getTopOfTree().equals(tree1) && node2.getTopOfTree().equals(tree2)) {
			tree1ToTree2.put(node1, node2);
			tree2ToTree1.put(node2, node1);
			tree2Nodes.add(node2);
		} else {
			throw new RuntimeException("Nodes cannot be linked: " + node1.toString() + "(" + node1.hashCode()
					+ ") || " + node2.toString() + "(" + node2.hashCode() + ")");
		}
	}
}
